// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © PueblaATH

//@version=6
indicator("Liquidity Sweeps & Swings (SMC/ICT) — TradingATH; PueblaATH", overlay=true, scale=scale.right, max_lines_count=500, max_labels_count=500, max_boxes_count=500)

// --- ANCLA DE ESCALA (invisible): fija el indicador a la escala del precio derecho (NO dibuja nada) ---
plot(close, title="__anchor__", display=display.none, editable=false)

// ============================  LIQUIDITY SWEEPS  ============================
groupLQ = "Liquidity Sweeps"

LQ_len    = input.int(5, 'Swings', minval=1, group=groupLQ)
LQ_opt    = input.string('Only Wicks', 'options', options=['Only Wicks', 'Only Outbreaks & Retest', 'Wicks + Outbreaks & Retest'], group=groupLQ)

LQ_colBl  = input.color(#089981, 'Bull ', group=groupLQ, inline='c1')
LQ_colBr  = input.color(#f23645, 'Bear' , group=groupLQ, inline='c2')
LQ_colBl2 = input.color(#08998180,  ''  , group=groupLQ, inline='c1')
LQ_colBr2 = input.color(#f2364580,  ''  , group=groupLQ, inline='c2')

groupArea = "Sweep Area"
LQ_extend = input.bool(true, 'Extend', group=groupArea)
LQ_maxB   = input.int(300, 'Max bars', minval=1, maxval=5000, group=groupArea)
LQ_colBl3 = input.color(#08998141, 'Bull ', group=groupArea)
LQ_colBr3 = input.color(#f2364541, 'Bear' , group=groupArea)

LQ_oW = LQ_opt == 'Only Wicks'
LQ_oO = LQ_opt == 'Only Outbreaks & Retest'

n = bar_index

// -- UDTs
type LQ_piv
    float prc
    int   bix
    bool  brk
    bool  mit
    bool  tak
    bool  wic
    line  lin

type LQ_boxBr
    box  bx
    line ln
    bool br
    int  dr

// -- Vars
var array<LQ_piv>   LQ_aPivH  = array.new<LQ_piv>()
var array<LQ_piv>   LQ_aPivL  = array.new<LQ_piv>()
var array<LQ_boxBr> LQ_aBoxBr = array.new<LQ_boxBr>()

// -- Helpers
method n(float piv) => bool out = not na(piv)
method p(LQ_piv piv, float val) => float out = (100 / piv.prc * val) - 100
method l(LQ_piv get, color c, string s='sd') =>
    style = switch s
        'dt' => line.style_dotted
        'ds' => line.style_dashed
        =>      line.style_solid
    line.new(get.bix, get.prc, n, get.prc, color=c, style=style)

// (mantener en UNA sola línea)
method br(LQ_piv get, color c3, color c, int d) =>
    y1 = d == 1 ? high : get.prc, y2 = d == 1 ? get.prc : low, LQ_boxBr.new(box.new(n-1, y1, n+1, y2, border_color=na, bgcolor=c3), line.new(n, y1, n, y2, color=c, width=3), false, d)

LQ_lnDot(y, c) => line.new(n, y, n+3, y, color=c, style=line.style_dotted)

// -- Execution
LQ_ph = ta.pivothigh(LQ_len, LQ_len)
LQ_pl = ta.pivotlow (LQ_len, LQ_len)

if LQ_ph.n()
    LQ_aPivH.unshift(LQ_piv.new(LQ_ph, n - LQ_len, false, false, false, false))
if LQ_pl.n()
    LQ_aPivL.unshift(LQ_piv.new(LQ_pl, n - LQ_len, false, false, false, false))

if array.size(LQ_aPivH) > 0
    for i = array.size(LQ_aPivH) - 1 to 0
        get = LQ_aPivH.get(i)
        if not get.mit
            if not get.brk
                if close > get.prc
                    if not LQ_oW
                        get.brk := true
                    else
                        get.mit := true
                if not LQ_oO and not get.wic
                    if high > get.prc and close < get.prc
                        LQ_aBoxBr.unshift(get.br(LQ_colBr3, LQ_colBr, 1))
                        get.l(LQ_colBr2, 'dt'), LQ_lnDot(low, LQ_colBr)
                        get.wic := true
            else
                if close < get.prc
                    get.mit := true
                if not LQ_oW and low < get.prc and close > get.prc
                    LQ_aBoxBr.unshift(get.br(LQ_colBl3, LQ_colBl, -1))
                    get.l(LQ_colBl2, 'ds'), LQ_lnDot(high, LQ_colBl)
                    get.tak := true
        if n - get.bix > 2000 or get.mit or get.tak
            line.delete(LQ_aPivH.remove(i).lin)

if array.size(LQ_aPivL) > 0
    for i = array.size(LQ_aPivL) - 1 to 0
        get = LQ_aPivL.get(i)
        if not get.mit
            if not get.brk
                if close < get.prc
                    if not LQ_oW
                        get.brk := true
                    else
                        get.mit := true
                if not LQ_oO and not get.wic
                    if low < get.prc and close > get.prc
                        LQ_aBoxBr.unshift(get.br(LQ_colBl3, LQ_colBl, -1))
                        get.l(LQ_colBl2, 'dt'), LQ_lnDot(high, LQ_colBl)
                        get.wic := true
            else
                if close > get.prc
                    get.mit := true
                if not LQ_oW and high > get.prc and close < get.prc
                    LQ_aBoxBr.unshift(get.br(LQ_colBr3, LQ_colBr, 1))
                    get.l(LQ_colBr2, 'ds'), LQ_lnDot(low, LQ_colBr)
                    get.tak := true
        if n - get.bix > 2000 or get.mit or get.tak
            line.delete(LQ_aPivL.remove(i).lin)

if LQ_extend
    for bx in LQ_aBoxBr
        if not bx.br and n - bx.bx.get_left() - 1 <= LQ_maxB
            bx.bx.set_right(bar_index)
            if bx.dr == -1 and close < bx.bx.get_bottom()
                bx.br := true
            if bx.dr ==  1 and close > bx.bx.get_top()
                bx.br := true

// ============================  LIQUIDITY SWINGS  ============================
groupLS = "Liquidity Swings"

LS_length = input.int(14, 'Pivot Lookback', group=groupLS)
LS_area   = input.string('Wick Extremity', 'Swing Area', options=['Wick Extremity', 'Full Range'], group=groupLS)

LS_intraPrecision = input.bool(false, 'Intrabar Precision', inline='intrabar', group=groupLS)
LS_intrabarTf     = input.timeframe('1', ''              , inline='intrabar', group=groupLS)

LS_filterOptions = input.string('Count', 'Filter Areas By', options=['Count', 'Volume'], inline='filter', group=groupLS)
LS_filterValue   = input.float(0, ''                                            , inline='filter', group=groupLS)

// Style
groupStyle = "Style"
LS_showTop    = input.bool(true,  'Swing High', inline='top', group=groupStyle)
LS_topCss     = input.color(color.red, '', inline='top', group=groupStyle)
LS_topAreaCss = input.color(color.new(color.red, 50), 'Area', inline='top', group=groupStyle)

LS_showBtm    = input.bool(true,  'Swing Low',  inline='btm', group=groupStyle)
LS_btmCss     = input.color(color.teal, '', inline='btm', group=groupStyle)
LS_btmAreaCss = input.color(color.new(color.teal, 50), 'Area', inline='btm', group=groupStyle)

LS_labelSize = input.string('Tiny', 'Labels Size', options=['Tiny', 'Small', 'Normal'], group=groupStyle)

// Extra (texto de etiqueta)
groupExtra = "Swing Labels (extra)"
LS_showSwingText  = input.bool(true, "Show 'Swing High / Low' labels", group=groupExtra)
LS_swingTextColor = input.color(color.white, 'Text color', group=groupExtra)
LS_swingBgColor   = input.color(color.new(color.black, 0), 'Label background', group=groupExtra)

// Intrabar data
LS_get_data()=> [high, low, volume]
[LS_h, LS_l, LS_v] = request.security_lower_tf(syminfo.tickerid, LS_intrabarTf, LS_get_data())

LS_get_counts(condition, top, btm)=>
    var count = 0
    var vol = 0.
    if condition
        count := 0
        vol := 0.
    else
        if LS_intraPrecision
            if n > LS_length
                if array.size(LS_v[LS_length]) > 0
                    for [index, element] in LS_v[LS_length]
                        vol += (array.get(LS_l[LS_length], index) < top and array.get(LS_h[LS_length], index) > btm) ? element : 0
        else
            vol += (low[LS_length] < top and high[LS_length] > btm) ? volume[LS_length] : 0
        count += (low[LS_length] < top and high[LS_length] > btm) ? 1 : 0
    [count, vol]

LS_set_level(condition, crossed, value, count, vol, css)=>
    var line lvl = na
    target = switch LS_filterOptions
        'Count'  => count
        'Volume' => vol
    if condition
        if target[1] < LS_filterValue[1]
            line.delete(lvl[1])
        else if not crossed[1]
            line.set_x2(lvl, n - LS_length)
        lvl := line.new(n - LS_length, value, n, value, color=na)
    if not crossed[1]
        line.set_x2(lvl, n + 3)
    if crossed and not crossed[1]
        line.set_x2(lvl, n)
        line.set_style(lvl, line.style_dashed)
    if target > LS_filterValue
        line.set_color(lvl, css)

LS_set_zone(condition, x, top, btm, count, vol, css)=>
    var box bx = na
    target = switch LS_filterOptions
        'Count'  => count
        'Volume' => vol
    if ta.crossover(target, LS_filterValue)
        bx := box.new(x, top, x + count, btm, border_color=na, bgcolor=css)
    if target > LS_filterValue
        box.set_right(bx, x + count)

// -- Globals (áreas actuales)
var float LS_ph_top = na
var float LS_ph_btm = na
var bool  LS_ph_crossed = false
var       LS_ph_x1 = 0
var box   LS_ph_bx = box.new(na, na, na, na, bgcolor=color.new(LS_topAreaCss, 80), border_color=na)

var float LS_pl_top = na
var float LS_pl_btm = na
var bool  LS_pl_crossed = false
var       LS_pl_x1 = 0
var box   LS_pl_bx = box.new(na, na, na, na, bgcolor=color.new(LS_btmAreaCss, 80), border_color=na)

// === Memoria para NO redibujar etiquetas (fijo 1 vez por pivote)
var int last_sh_bar = na
var int last_sl_bar = na

// ================== Pivot High ==================
LS_ph = ta.pivothigh(LS_length, LS_length)
phCond = not na(LS_ph)
[LS_ph_count, LS_ph_vol] = LS_get_counts(phCond, LS_ph_top, LS_ph_btm)

if phCond and LS_showTop
    LS_ph_top := high[LS_length]
    LS_ph_btm := switch LS_area
        'Wick Extremity' => math.max(close[LS_length], open[LS_length])
        'Full Range'     => low[LS_length]
    LS_ph_x1 := n - LS_length
    LS_ph_crossed := false
    box.set_lefttop(LS_ph_bx, LS_ph_x1, LS_ph_top)
    box.set_rightbottom(LS_ph_bx, LS_ph_x1, LS_ph_btm)
else
    LS_ph_crossed := close > LS_ph_top ? true : LS_ph_crossed
    box.set_right(LS_ph_bx, LS_ph_crossed ? LS_ph_x1 : n + 3)

if LS_showTop
    LS_set_zone(phCond, LS_ph_x1, LS_ph_top, LS_ph_btm, LS_ph_count, LS_ph_vol, LS_topAreaCss)
    LS_set_level(phCond, LS_ph_crossed, LS_ph_top, LS_ph_count, LS_ph_vol, LS_topCss)

// ---- Etiqueta Swing High (crear SOLO una vez por pivote confirmado)
if phCond
    pivBar = n - LS_length
    if na(last_sh_bar) or pivBar != last_sh_bar
        last_sh_bar := pivBar
        label.new(pivBar, high[LS_length], "Swing High", xloc=xloc.bar_index, yloc=yloc.price, style=label.style_label_down, textcolor=LS_swingTextColor, color=LS_swingBgColor, size=size.small)

// ================== Pivot Low ===================
LS_pl = ta.pivotlow(LS_length, LS_length)
plCond = not na(LS_pl)
[LS_pl_count, LS_pl_vol] = LS_get_counts(plCond, LS_pl_top, LS_pl_btm)

if plCond and LS_showBtm
    LS_pl_top := switch LS_area
        'Wick Extremity' => math.min(close[LS_length], open[LS_length])
        'Full Range'     => high[LS_length]
    LS_pl_btm := low[LS_length]
    LS_pl_x1 := n - LS_length
    LS_pl_crossed := false
    box.set_lefttop(LS_pl_bx, LS_pl_x1, LS_pl_top)
    box.set_rightbottom(LS_pl_bx, LS_pl_x1, LS_pl_btm)
else
    LS_pl_crossed := close < LS_pl_btm ? true : LS_pl_crossed
    box.set_right(LS_pl_bx, LS_pl_crossed ? LS_pl_x1 : n + 3)

if LS_showBtm
    LS_set_zone(plCond, LS_pl_x1, LS_pl_top, LS_pl_btm, LS_pl_count, LS_pl_vol, LS_btmAreaCss)
    LS_set_level(plCond, LS_pl_crossed, LS_pl_btm, LS_pl_count, LS_pl_vol, LS_btmCss)

// ---- Etiqueta Swing Low (crear SOLO una vez por pivote confirmado)
if plCond
    pivBarL = n - LS_length
    if na(last_sl_bar) or pivBarL != last_sl_bar
        last_sl_bar := pivBarL
        label.new(pivBarL, low[LS_length], "Swing Low", xloc=xloc.bar_index, yloc=yloc.price, style=label.style_label_up, textcolor=LS_swingTextColor, color=LS_swingBgColor, size=size.small)
